/* Filename: mydu.c
 * Description: 递归遍历目录，计算目录总大小
 * Last modified: humble 20200314 18:20
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>

#define PATHSIZE (1024)

static void usage(void);
static int errfunc_(const char *epath, int eerrno);

static int path_noloop(const char *pathname) { // 判断是.或者..目录就返回0，其它的返回1
    char *ptr = strrchr(pathname, '/'); // strchr是从头往后找；strrchr是从尾往前找。查找路径名中最后一个 '/' 字符的位置
    if(ptr == NULL){ exit(0); }

    if((!strcmp(ptr + 1, ".")) || (!strcmp(ptr + 1, ".."))){
        return 0;
    }
    return 1;
}

static int64_t mydu(const char *pathname) { // glob递归遍历目录，计算占用磁盘的总blocks数
    static char nextpathname[PATHSIZE] ={0}; // 静态变量，存储递归的目录环境

    static struct stat statres; // 使用静态变量是为了减少使用栈空间，免得用光后段错误
    if(lstat(pathname, &statres) < 0){ perror("lstat"); exit(1); } // 记得用lstat别用stat

    //非目录文件
    if(!S_ISDIR(statres.st_mode)){ // 递归返回条件
        printf("%s is not a directory: %ld\n", pathname, statres.st_blocks);
        // st_size是指文件内容的大小，st_blocks*512是指占用物理存储的总大小
        return statres.st_blocks; // 占用了多少块st_blocks，每一块512字节
    }
    //是目录则遍历

    glob_t globres;
    strncpy(nextpathname, pathname, PATHSIZE); // 递归的子目录会拼接到末尾
    strncat(nextpathname, "/*", PATHSIZE); // 只找非隐藏文件
    //printf("nextpathname:%s\n", nextpathname);
    int ret = glob(nextpathname, 0, errfunc_, &globres);
    if(ret){ printf("glob errcode=%d\n", ret); exit(1); }

    strncpy(nextpathname, pathname, PATHSIZE);
    strncat(nextpathname, "/.*", PATHSIZE); // 只找隐藏文件
    //printf("nextpathname:%s\n", nextpathname);
    ret = glob(nextpathname, GLOB_APPEND, errfunc_, &globres); // APPEND 追加保存到globres
    if(ret){ printf("glob errcode=%d\n", ret); exit(1); }

    int64_t sum = statres.st_blocks; // 先存储当前目录自身的大小
    for(size_t i = 0; i < globres.gl_pathc; i++) {
        //printf("%s\n", globres.gl_pathv[i]);
        if(path_noloop(globres.gl_pathv[i])){
            sum += mydu(globres.gl_pathv[i]); // 若是文件，传进去则返回大小；若是目录传进去则递归
        }
    }

    globfree(&globres);
    return sum;
}

int main(int argc, char **argv) {
    if(argc != 2){ fprintf(stderr, "argc !=2"); usage(); exit(1); }
    printf("%ld\n", mydu(argv[1])/2); // 除以 2 是为了把以 512 字节为单位的磁盘块数量转换为以 1024 字节为单位
    return 0;
}

static void usage(void) {
#define USAGE "Usage:\n\
  Helloworld\n"
    printf(USAGE);
}

static int errfunc_(const char *epath, int eerrno) {
    fprintf(stderr, "Error: %s: %s\n", epath, strerror(eerrno));
    return 0;
}
